package org.example.lotterysystem.service.impl;

import org.example.lotterysystem.common.errorcode.ServiceErrorCode;
import org.example.lotterysystem.common.utils.JacksonUtil;
import org.example.lotterysystem.common.utils.RedisUtil;
import org.example.lotterysystem.controller.param.DrawPrizeParam;
import org.example.lotterysystem.controller.param.ShowWinningRecordsParam;
import org.example.lotterysystem.dao.dataobject.*;
import org.example.lotterysystem.dao.mapper.*;
import org.example.lotterysystem.service.DrawPrizeService;
import org.example.lotterysystem.service.dto.WinningRecordDTO;
import org.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import org.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import org.example.lotterysystem.service.enums.ActivityStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

import static org.example.lotterysystem.common.config.DirectRabbitConfig.EXCHANGE_NAME;
import static org.example.lotterysystem.common.config.DirectRabbitConfig.ROUTING;

@Service
public class DrawPrizeServiceImpl implements DrawPrizeService {
    
    private static final Logger log = LoggerFactory.getLogger(DrawPrizeServiceImpl.class);
    private final String WINNING_RECORDS_PREFIX = "WINNING_RECORDS_";
    private final Long WINNING_RECORDS_TIMEOUT = 60*60*24*2L;

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private ActivityMapper activityMapper;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PrizeMapper prizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;
    @Autowired
    private RedisUtil redisUtil;

    @Override
    public void drawPrize(DrawPrizeParam param) {
        Map<String, String> map = new HashMap<>();
        map.put("messageId", String.valueOf(UUID.randomUUID()));
        map.put("messageData", JacksonUtil.writeValueAsString(param));
        //发消息
        rabbitTemplate.convertAndSend(EXCHANGE_NAME, ROUTING, map);
        log.info("mq消息发送成功:map={}", JacksonUtil.writeValueAsString(map));
    }

    @Override
    public Boolean checkDrawPrizeParam(DrawPrizeParam param) {
        ActivityDO activityDO = activityMapper.selectById(param.getActivityId());
        //奖品是否存在从activity_prize查,原因是做了本地事务,保证了一致性
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByAPId(
                param.getActivityId(), param.getPrizeId());
        //活动或者奖品是否存在
        if(null == activityDO || null == activityPrizeDO) {
            //throw new ServiceException(ServiceErrorCode.ACTIVITY_OR_PRIZE_IS_EMPTY);
            log.info("校验抽奖请求失败,:{}", ServiceErrorCode.ACTIVITY_OR_PRIZE_IS_EMPTY.getMessage());
            return false;
        }
        //活动是否有效
        if(activityDO.getStatus().equalsIgnoreCase(ActivityStatusEnum.COMPLETED.name())) {
            //throw new ServiceException(ServiceErrorCode.ACTIVITY_COMPLETED);
            log.info("校验抽奖请求失败,:{}", ServiceErrorCode.ACTIVITY_COMPLETED.getMessage());
            return false;
        }
        //奖品是否有效
        if(activityPrizeDO.getStatus().equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name())) {
            //throw new ServiceException(ServiceErrorCode.ACTIVITY_PRIZE_COMPLETED);
            log.info("校验抽奖请求失败,:{}", ServiceErrorCode.ACTIVITY_PRIZE_COMPLETED.getMessage());
            return false;
        }
        //中奖者人数是否和设置奖品数量一致
        if(activityPrizeDO.getPrizeAmount() != param.getWinnerList().size()){
            //throw new ServiceException(ServiceErrorCode.WINNER_PRIZE_AMOUNT_ERROR);
            log.info("校验抽奖请求失败,:{}", ServiceErrorCode.WINNER_PRIZE_AMOUNT_ERROR.getMessage());
            return false;
        }
        return true;
    }

    @Override
    public List<WinningRecordDO> saveWinnerRecords(DrawPrizeParam param) {
        //查询相关信息
        ActivityDO activityDO = activityMapper.selectById(param.getActivityId());
        List<UserDO> userDOList = userMapper.batchSelectByIds(param.getWinnerList().stream()
                .map(DrawPrizeParam.Winner::getUserId).collect(Collectors.toList()));
        PrizeDO prizeDO = prizeMapper.selectById(param.getPrizeId());
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());
        //保存中奖者记录
        List<WinningRecordDO> winningRecordDOList = userDOList.stream()
                .map(userDO -> {
                    WinningRecordDO winningRecordDO = new WinningRecordDO();
                    winningRecordDO.setActivityId(activityDO.getId());
                    winningRecordDO.setActivityName(activityDO.getActivityName());
                    winningRecordDO.setPrizeId(prizeDO.getId());
                    winningRecordDO.setPrizeName(prizeDO.getName());
                    winningRecordDO.setPrizeTier(activityPrizeDO.getPrizeTiers());
                    winningRecordDO.setWinnerId(userDO.getId());
                    winningRecordDO.setWinnerName(userDO.getUserName());
                    winningRecordDO.setWinnerEmail(userDO.getEmail());
                    winningRecordDO.setWinnerPhoneNumber(userDO.getPhoneNumber());
                    winningRecordDO.setWinningTime(param.getWinningTime());
                    return winningRecordDO;
                }).collect(Collectors.toList());
        winningRecordMapper.batchInsert(winningRecordDOList);
        //缓存中奖者记录
        //1.奖品维度
        cacheWinningRecords(param.getActivityId() + "_" + param.getPrizeId(), winningRecordDOList, WINNING_RECORDS_TIMEOUT);
        //2.活动维度
        //当活动已完成再去存放活动维度中奖记录
        if(activityDO.getStatus().equalsIgnoreCase(ActivityStatusEnum.COMPLETED.name())) {
            //查询活动维度的全量中奖记录
            List<WinningRecordDO> allList = winningRecordMapper.selectByActivityId(param.getActivityId());
            cacheWinningRecords(String.valueOf(param.getActivityId()), allList, WINNING_RECORDS_TIMEOUT);
        }
        return winningRecordDOList;
    }

    @Override
    public void deleteRecords(Long activityId, Long prizeId) {
        if (null == activityId) {
            log.warn("要删除中奖记录相关活动id为空");
            return;
        }
        //删除数据表
        winningRecordMapper.deleteRecords(activityId, prizeId);

        //删除缓存
        if(null != prizeId) {
            deleteWinningRecords(activityId+"_"+prizeId);
        }
        //无论是否传递了prizeId,都需要剔除活动维度的中奖记录缓存
        //如果传递了prizeId,证明奖品未抽取,必须剔除活动维度的缓存记录
        //如果没有传递prizeId,就只是剔除活动维度的信息
        deleteWinningRecords(String.valueOf(activityId));
    }

    @Override
    public List<WinningRecordDTO> getRecords(ShowWinningRecordsParam param) {
        //查询redis
        String key = null == param.getPrizeId() ? String.valueOf(param.getActivityId())
                : param.getActivityId()+"_"+param.getPrizeId();
        List<WinningRecordDO> winningRecordDOList = getWinningRecords(key);
        if(!CollectionUtils.isEmpty(winningRecordDOList)) {
            return convertToWinningRecordDTOList(winningRecordDOList);
        }
        //如果redis不存在,查库
        winningRecordDOList = winningRecordMapper.selectByActivityIdOrPrizeId(param.getActivityId(), param.getPrizeId());
        //存放记录到redis中
        if(CollectionUtils.isEmpty(winningRecordDOList)) {
           log.info("查询的中奖记录为空, param:{}", JacksonUtil.writeValueAsString(param));
            return Arrays.asList();
        }
        cacheWinningRecords(key, winningRecordDOList, WINNING_RECORDS_TIMEOUT);
        return convertToWinningRecordDTOList(winningRecordDOList);
    }

    private List<WinningRecordDTO> convertToWinningRecordDTOList(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            return Arrays.asList();
        }
        return winningRecordDOList.stream()
                .map(winningRecordDO -> {
                    WinningRecordDTO winningRecordDTO = new WinningRecordDTO();
                    winningRecordDTO.setWinnerId(winningRecordDO.getWinnerId());
                    winningRecordDTO.setWinnerName(winningRecordDO.getWinnerName());
                    winningRecordDTO.setPrizeName(winningRecordDO.getPrizeName());
                    winningRecordDTO.setPrizeTier(ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()));
                    winningRecordDTO.setWinningTime(winningRecordDO.getWinningTime());
                    return winningRecordDTO;
                }).collect(Collectors.toList());
    }

    //从缓存中删除中奖记录
    private void deleteWinningRecords(String key) {
        try {
            if(redisUtil.hasKey(WINNING_RECORDS_PREFIX+key)) {
                //存在再删除
                redisUtil.del(WINNING_RECORDS_PREFIX+key);
            }
        } catch (Exception e) {
            log.error("删除中奖记录缓存异常,key:{}", key, e);
        }
    }

    //缓存中奖记录
    private void cacheWinningRecords(String key, List<WinningRecordDO> winningRecordDOList, Long time) {
        try {
            if(!StringUtils.hasText(key)
                || CollectionUtils.isEmpty(winningRecordDOList)) {
                log.warn("要缓存的内容为空,key:{},value:{}", key, JacksonUtil.writeValueAsString(winningRecordDOList));
                return;
            }
            redisUtil.set(WINNING_RECORDS_PREFIX+key,
                    JacksonUtil.writeValueAsString(winningRecordDOList),
                    time);
        } catch (Exception e) {
            log.error("缓存中奖记录异常!key:{},value:{}", WINNING_RECORDS_PREFIX+key, JacksonUtil.writeValueAsString(winningRecordDOList), e);
        }
    }
    //从缓存中获取中奖记录
    private List<WinningRecordDO> getWinningRecords(String key) {
        try {
            if(!StringUtils.hasText(key)) {
                log.warn("要从缓存中查询的中奖记录的key为空");
                return Arrays.asList();
            }
            String str = redisUtil.get(WINNING_RECORDS_PREFIX+key);
            if(!StringUtils.hasText(str)) {
                return Arrays.asList();
            }
            return JacksonUtil.readListValue(str, WinningRecordDO.class);
        } catch (Exception e) {
            log.error("查询缓存中奖记录异常!key:{}", WINNING_RECORDS_PREFIX+key, e);
            return Arrays.asList();
        }
    }
}
